package MyGui;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Vector;

import Element.AbsElem;
import Element.Source;
import Element.Geom.*;
import DistSys.Sys;

public class TroubleShootingDlg extends MyDlgFrame implements ActionListener,
                                                              WindowListener {
    private JCheckBox checkZeroSize;
    private JCheckBox checkOverlapping, checkCoordination;
    private JTextField tfCoordination;
    private int iCoordination = 0;
    private JCheckBox checkDangling, checkDeenergized;
    private JLabel lblMessage;
    private volatile ElemListScrollPane scrollPane;
    private JButton btnExecute, btnGoto, btnAddToSelection, btnSelect;
    private JButton btnHighLight, btnClearHighLights;
    private TroubleShootingThread threadSearch = null;

    private String[] asBtnExecuteText   = new String[] { "Execute", "Stop" };
    private String[] asBtnGotoText      = new String[] { "Go to",
                                                         "Go to & Highlight" };
    private String[] asBtnHighLightText = new String[] { "Turn Highlight On",
                                                         "Turn Highlight Off" };
    private int iIsHighLightOn = 0;

    public TroubleShootingDlg(MainFrame parent, String title) {
        super(parent, title);
        JPanel searchPanel = new JPanel(gridBagLayout);
        searchPanel.setBorder(BorderFactory.createTitledBorder(
            BorderFactory.createEtchedBorder(), " Search for "));

        checkZeroSize = new JCheckBox("Zero-length(visual) components");
        checkZeroSize.setMnemonic('C');
        checkZeroSize.setSelected(true);
        reduceCheckBoxHeight(checkZeroSize);
        checkOverlapping = new JCheckBox("Overlapping components");
        checkOverlapping.setMnemonic('O');
        checkOverlapping.setSelected(true);
        reduceCheckBoxHeight(checkOverlapping);
        checkCoordination = new JCheckBox("Coordination: more than");
        checkCoordination.setMnemonic('R');
        checkCoordination.setSelected(true);
        reduceCheckBoxHeight(checkCoordination);
        tfCoordination = new JTextField("3", 3);
        tfCoordination.setInputVerifier(new PosAndZeroIntVerifier());
        JLabel lblCoordination = new JLabel("protection devices in series");
        lblCoordination.setForeground(checkCoordination.getForeground());
        checkDangling = new JCheckBox("Dangling series components");
        checkDangling.setMnemonic('D');
        checkDangling.setSelected(true);
        reduceCheckBoxHeight(checkDangling);
        checkDeenergized = new JCheckBox("Deenergized components");
        checkDeenergized.setMnemonic('E');
        checkDeenergized.setSelected(true);
        reduceCheckBoxHeight(checkDeenergized);
        lblMessage = new JLabel();

        GridBagConstraints c = new GridBagConstraints();
        c.insets = new Insets(-2, GAP, 0, GAP);
        c.weightx = 1; c.anchor = GridBagConstraints.WEST;
        c.gridy = 0;
        c.gridx = 0; c.gridwidth = 3; searchPanel.add(checkZeroSize, c);
        c.gridx = 3; c.gridwidth = 1; searchPanel.add(checkOverlapping, c);
        c.gridy = 1; c.gridwidth = 1; c.weightx = 0;
        c.insets.top = c.insets.right = 0;
        c.gridx = 0; searchPanel.add(checkCoordination, c);
        c.gridx = 1; c.insets.left = 0; searchPanel.add(tfCoordination, c);
        c.weightx = 1; c.insets.left = c.insets.right = GAP;
        c.gridx = 2; c.gridwidth = 2; searchPanel.add(lblCoordination, c);
        c.gridy = 2; c.weightx = 1;
        c.gridx = 0; c.gridwidth = 3; searchPanel.add(checkDangling, c);
        c.gridx = 3; c.gridwidth = 1; searchPanel.add(checkDeenergized, c);
        c.gridy = 3; c.insets.top = GAP/2; c.insets.bottom = GAP;
        c.gridx = 0; c.gridwidth = 4; searchPanel.add(lblMessage, c);

        scrollPane = new ElemListScrollPane(mainFrame,
            ElemListTableModel.PANE_TROUBLE);
        scrollPane.getTable().addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent evt) {
                if (iIsHighLightOn != 0 && evt.getClickCount() == 2) {
                    scrollPane.highLight();
                }
            }
        });

        JPanel southPanel = new JPanel(new GridLayout(2, 3, GAP, GAP));
        btnExecute = new JButton(asBtnExecuteText[0]);
        btnExecute.setMnemonic('X');
        btnExecute.addActionListener( this );
        btnGoto = new JButton(asBtnGotoText[iIsHighLightOn]);
        btnGoto.setMnemonic('G');
        btnGoto.addActionListener( this );
        btnAddToSelection = new JButton("Add to Selection");
        btnAddToSelection.setMnemonic('A');
        btnAddToSelection.addActionListener( this );
        btnSelect = new JButton("Select");
        btnSelect.setMnemonic('S');
        btnSelect.addActionListener( this );
        btnHighLight = new JButton(asBtnHighLightText[iIsHighLightOn]);
        btnHighLight.setMnemonic('H');
        btnHighLight.addActionListener( this );
        btnClearHighLights = new JButton("Clear Highlights");
        btnClearHighLights.setMnemonic('L');
        btnClearHighLights.addActionListener( this );
        southPanel.add(btnExecute);
        southPanel.add(btnGoto);
        southPanel.add(btnHighLight);
        southPanel.add(btnSelect);
        southPanel.add(btnAddToSelection);
        southPanel.add(btnClearHighLights);

        Container pane = this.getContentPane();
        pane.setLayout(new BorderLayout(GAP, GAP));
        pane.add(searchPanel, BorderLayout.NORTH);
        pane.add(scrollPane, BorderLayout.CENTER);
        pane.add(southPanel, BorderLayout.SOUTH);

        this.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
        this.setSize(450, 500);
        this.addWindowListener(this);
    }

    private void reduceCheckBoxHeight(JCheckBox check) {
        Dimension dim = check.getPreferredSize();
        dim.height -= 2;
        check.setPreferredSize(dim);
    }

    public void actionPerformed(ActionEvent evt){
        Object o = evt.getSource();
        if( o == btnExecute ){
            if (threadSearch != null && threadSearch.isAlive()) {
                // stop thread
                threadSearch.pleaseStop();
            } else {
                // start thread
                threadSearch = new TroubleShootingThread();
                threadSearch.start();
            }
        } else if (o == btnGoto) {
            scrollPane.gotoSelectedElem();
            if (iIsHighLightOn != 0)
                scrollPane.highLight();
        } else if (o == btnAddToSelection) {
            scrollPane.addToSelection();
        } else if (o == btnSelect) {
            scrollPane.select();
        } else if (o == btnHighLight) {
            iIsHighLightOn = 1 - iIsHighLightOn;
            btnGoto.setText(asBtnGotoText[iIsHighLightOn]);
            btnHighLight.setText(asBtnHighLightText[iIsHighLightOn]);
            if (iIsHighLightOn == 0)
                scrollPane.clearHighLights();
        } else if (o == btnClearHighLights) {
            scrollPane.clearHighLights();
        }
    }

    public void windowOpened( WindowEvent evt ){ }
    public void windowClosed( WindowEvent evt ){ }
    public void windowIconified( WindowEvent evt ){ }
    public void windowDeiconified( WindowEvent evt ){ }
    public void windowActivated( WindowEvent evt ){ scrollPane.updateTable(); }
    public void windowDeactivated( WindowEvent evt ){ }
    public void windowClosing( WindowEvent evt ){
        if (threadSearch != null && threadSearch.isAlive()) {
            try {
                threadSearch.pleaseStop();
                threadSearch.join(1000); // wait at most one second
            } catch(InterruptedException e) {
                // do nothing about it.
            }
        }
        scrollPane.clearHighLights();
    }

    class TroubleShootingThread extends Thread {
        private volatile boolean bStop = false;
        private boolean bZeroSize, bOverlapping;
        private boolean bCoordination;
        private int iCoordination;
        private boolean bDangling, bDeenergized;
        private int iLastRowCount;

        public TroubleShootingThread() {
            bStop = false;
            mainFrame.getContentPane().setCursor(
                Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            scrollPane.getData().removeAllElements();
            scrollPane.getModel().fireTableDataChanged();
            iLastRowCount = 0;
            btnExecute.setText(asBtnExecuteText[1]);
            bZeroSize     = checkZeroSize.isSelected();
            bOverlapping  = checkOverlapping.isSelected();
            bCoordination = checkCoordination.isSelected();
            iCoordination = Integer.parseInt(tfCoordination.getText());
            bDangling     = checkDangling.isSelected();
            bDeenergized  = checkDeenergized.isSelected();
        }
        public void pleaseStop() { bStop = true; }

        public void run() {
            try {
                // Zero size components
                if (bZeroSize) {
                    lblMessage.setText("Searching for zero size components...");
                    findZeroSize();
                    if (postProcess())
                        return;
                }
                // Overlapping components
                if (bOverlapping) {
                    lblMessage.setText(
                        "Searching for overlapping components...");
                    findOverlapping();
                    if (postProcess())
                        return;
                }
                // Coordination
                if (bCoordination) {
                    lblMessage.setText("Searching for more than " +
                        iCoordination + " protection devices in series...");
                    findCoordination();
                    if (postProcess())
                        return;
                }
                // Dangling components
                if (bDangling) {
                    lblMessage.setText(
                        "Searching for dangling series components...");
                    findDangling();
                    if (postProcess())
                        return;
                }
                // Deenergized components
                if (bDeenergized) {
                    lblMessage.setText(
                        "Searching for deenergized components...");
                    findDeenergized();
                    if (postProcess())
                        return;
                }

                lblMessage.setText("" + scrollPane.getData().size() +
                    " matches found");
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
                btnExecute.setText(asBtnExecuteText[0]);
                mainFrame.getContentPane().setCursor(
                    Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }

        public final boolean postProcess() {
            if (bStop) {
                    lblMessage.setText(
                        "Cancelled by user");
            } else {
                ElemListTableModel model = scrollPane.getModel();
                int iRowCount = model.getRowCount();
                model.fireTableRowsInserted(iLastRowCount, iRowCount-1);
                iLastRowCount = iRowCount;
            }
            return bStop;
        }

        // Trouble: Zero size components
        private final boolean zeroSize(AbsElemGeom geom) {
            return (geom.x1Stored == geom.x2Stored &&
                    geom.y1Stored == geom.y2Stored);
        }

        private class ZeroSizeElem extends ElemListAbsElemEntry {
            public ZeroSizeElem(AbsElem elem) { super(elem); }
            public boolean updateElem() {
                return (super.updateElem() && zeroSize(elem.getElemGeomObj()));
            }
            public Object getElemValue(int iField) {
                if (iField == ElemListTableModel.FIELD_MSG)
                    return "Zero size component";
                else return super.getElemValue(iField);
            }
        }

        private void findZeroSize() {
            int size = Sys.elemList.size();
            for (int i = 0; !bStop && i < size; i++) {
                AbsElem elem = (AbsElem) Sys.elemList.elementAt(i);
                if (zeroSize(elem.getElemGeomObj())) {
                    scrollPane.getData().add(new ZeroSizeElem(elem));
                }
            }
        }

        // Trouble: Overlapping components
        private final boolean overlapping(AbsElemGeom geom1, AbsElemGeom geom2){
            return (geom1.x1Stored == geom2.x1Stored &&
                    geom1.y1Stored == geom2.y1Stored &&
                    geom1.x2Stored == geom2.x2Stored &&
                    geom1.y2Stored == geom2.y2Stored);
        }

        private class OverlappingElemVector extends ElemListVectorEntry {
            public boolean updateElem() {
                if (super.updateElem()) {
                    int j, i = 0;
                    while (i < size()) {
                        AbsElem elem = (AbsElem)elementAt(i);
                        AbsElemGeom geom = elem.getElemGeomObj();
                        for (j = 0; j < size(); j++) {
                            if (i != j) {
                                AbsElem elem2 = ((AbsElem)elementAt(j));
                                if (overlapping(geom, elem2.getElemGeomObj()))
                                    break;
                            }
                        }
                        if (j < size()) {
                            // still a overlapping element found
                            i++;
                        } else {
                            removeElementAt(i);
                            // We must not increase i!
                        }
                    }
                    return !isEmpty();
                } else
                    return false;
            }

            public Object getElemValue(int iField) {
                if (iField == ElemListTableModel.FIELD_MSG)
                    return "Overlapping components";
                else return super.getElemValue(iField);
            }
        }

        private void findOverlapping() {
            // vOverlappedElem holds all overlapping elements so that
            // they are not checked again when it is their turn.
            Vector vOverlappedElem = new Vector();
            int size = Sys.elemList.size();
            for (int i = 0; !bStop && i < size; i++) {
                AbsElem elem = (AbsElem) Sys.elemList.elementAt(i);
                if (!vOverlappedElem.contains(elem)) {
                    // elem was not found overlapping before
                    AbsElemGeom geom = elem.getElemGeomObj();
                    OverlappingElemVector vElem = null;
                    for (int j = i+1; j < size; j++) {
                        AbsElem elem2 = (AbsElem) Sys.elemList.elementAt(j);
                        if (overlapping(geom, elem2.getElemGeomObj())) {
                            if (vElem == null) {
                                // First time for elem
                                vElem = new OverlappingElemVector();
                                // Add first element
                                vElem.add(elem);
                                // We do not need to add it to vOverlappedElem
                                // since it it its turn now and we do not need
                                // to prevent it from been checked again.
                            }
                            // Add this elem2
                            vElem.add(elem2);
                            vOverlappedElem.add(elem2);
                        }
                    }
                    if (vElem != null) {
                        // Overlapping components found
                        scrollPane.getData().add(vElem);
                    }
                }
            }
        }

        // Trouble: Coordination
        private class CoordinationElemVector extends ElemListVectorEntry {
            public boolean updateElem() {
                return (super.updateElem() && size() > iCoordination);
            }

            public Object getElemValue(int iField) {
                if (iField == ElemListTableModel.FIELD_MSG)
                    return "Coordination";
                else return super.getElemValue(iField);
            }
        }

        private void findCoordination() {
            Sys.TraceSource.CoordinationList coordList =
                new Sys.TraceSource.CoordinationList() {
                Vector vLastProblem = null;
                public void add(Vector vElems, Vector vProtectionElems) {
                    if (vProtectionElems.size() > iCoordination &&
                        (vLastProblem == null ||
                         !vLastProblem.containsAll(vProtectionElems))) {
                        // If this vector of protection devices is _not_ a
                        // subset of the last vector, this is a _new_
                        // coordination problem. So we need to add it.
                        // (This works since we do a depth first search.)
                        CoordinationElemVector v =
                            new CoordinationElemVector();
                        // Note: We need to copy vProtectionElems anyway
                        //       @see Sys.TraceSource.CoordinationList
                        v.addAll(vProtectionElems);
                        scrollPane.getData().add(v);
                        // set the last problem vector
                        vLastProblem = v;
                    }
                }
            };

            int size = Sys.elemList.size();
            for (int i = 0; !bStop && i < size; i++) {
                AbsElem elem = (AbsElem) Sys.elemList.elementAt(i);
                if (elem instanceof Source) {
                    Sys.TraceSource.getCoordinationOfSource(elem,coordList);
                }
            }
        }

        // Trouble: Dangling series components
        private boolean dangling(AbsElemGeom geom) {
            return (!(geom instanceof OneNodeElemGeom) &&
                    (geom.conn1.isEmpty() ||
                     (!(geom instanceof BusGeom) &&
                      geom.conn2.isEmpty()) ||
                     (geom instanceof ThreeNodeElemGeom &&
                      ((ThreeNodeElemGeom)geom).conn3.isEmpty())));
        }

        private class DanglingElem extends ElemListAbsElemEntry {
            public DanglingElem(AbsElem elem) { super(elem); }
            public boolean updateElem() {
                return (super.updateElem() && dangling(elem.getElemGeomObj()));
            }
            public Object getElemValue(int iField) {
                if (iField == ElemListTableModel.FIELD_MSG)
                    return "Dangling component";
                else return super.getElemValue(iField);
            }
        }

        private void findDangling() {
            int size = Sys.elemList.size();
            for (int i = 0; !bStop && i < size; i++) {
                AbsElem elem = (AbsElem) Sys.elemList.elementAt(i);
                AbsElemGeom geom = elem.getElemGeomObj();
                if ((geom instanceof TwoNodeElemGeom ||
                     geom instanceof ThreeNodeElemGeom) &&
                    (geom.conn1.isEmpty() ||
                     (!(geom instanceof BusGeom) &&
                      geom.conn2.isEmpty()) ||
                     (geom instanceof ThreeNodeElemGeom &&
                      ((ThreeNodeElemGeom)geom).conn3.isEmpty()))) {
                    scrollPane.getData().add(new DanglingElem(elem));
                }
            }
        }

        // Trouble: Deenergized components
        private void findDeenergized() {
            Sys.assignVoltageLevelToAllElements(true); // block warning message
            int size = Sys.elemList.size();
            for (int i = 0; !bStop && i < size; i++) {
                AbsElem elem = (AbsElem) Sys.elemList.elementAt(i);
                if (elem.getResult(AbsElem.RES_NOMINAL_VOLT) == 0.0) {
                    AbsElemGeom geom = elem.getElemGeomObj();
                    if (geom instanceof GroupGeom ||
                        (geom instanceof SwtGeom &&
                         ((SwtGeom)geom).getIsEnergized())) {
                        // this is a energized element with no nominal voltage
                        // by algorithm design
                        continue;
                    }

                    elem.setMsgForLog("Deenergized component");
                    scrollPane.getData().add(elem);
                }
            }
        }
    }
}//end of class TroubleShootingDlg